home *** CD-ROM | disk | FTP | other *** search
/ Freelog 125 / Freelog_MarsAvril2015_No125.iso / ViePratique / ArchiFacile / ArchiFacileSetup.exe / {app} / nw.pak / Unnamed File 001142.txt < prev    next >
Text File  |  2014-10-14  |  39KB  |  1,197 lines

  1. // Copyright 2013 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. var media = {};
  6.  
  7. // Copyright 2013 The Chromium Authors. All rights reserved.
  8. // Use of this source code is governed by a BSD-style license that can be
  9. // found in the LICENSE file.
  10.  
  11. /**
  12.  * A global object that gets used by the C++ interface.
  13.  */
  14. var media = (function() {
  15.   'use strict';
  16.  
  17.   var manager = null;
  18.  
  19.   // A number->string mapping that is populated through the backend that
  20.   // describes the phase that the network entity is in.
  21.   var eventPhases = {};
  22.  
  23.   // A number->string mapping that is populated through the backend that
  24.   // describes the type of event sent from the network.
  25.   var eventTypes = {};
  26.  
  27.   // A mapping of number->CacheEntry where the number is a unique id for that
  28.   // network request.
  29.   var cacheEntries = {};
  30.  
  31.   // A mapping of url->CacheEntity where the url is the url of the resource.
  32.   var cacheEntriesByKey = {};
  33.  
  34.   var requrestURLs = {};
  35.  
  36.   var media = {
  37.     BAR_WIDTH: 200,
  38.     BAR_HEIGHT: 25
  39.   };
  40.  
  41.   /**
  42.    * Users of |media| must call initialize prior to calling other methods.
  43.    */
  44.   media.initialize = function(theManager) {
  45.     manager = theManager;
  46.   };
  47.  
  48.   media.onReceiveEverything = function(everything) {
  49.     for (var component in everything) {
  50.       media.updateAudioComponent(everything[component]);
  51.     }
  52.   };
  53.  
  54.   media.onReceiveConstants = function(constants) {
  55.     for (var key in constants.eventTypes) {
  56.       var value = constants.eventTypes[key];
  57.       eventTypes[value] = key;
  58.     }
  59.  
  60.     for (var key in constants.eventPhases) {
  61.       var value = constants.eventPhases[key];
  62.       eventPhases[value] = key;
  63.     }
  64.   };
  65.  
  66.   media.cacheForUrl = function(url) {
  67.     return cacheEntriesByKey[url];
  68.   };
  69.  
  70.   media.onNetUpdate = function(updates) {
  71.     updates.forEach(function(update) {
  72.       var id = update.source.id;
  73.       if (!cacheEntries[id])
  74.         cacheEntries[id] = new media.CacheEntry;
  75.  
  76.       switch (eventPhases[update.phase] + '.' + eventTypes[update.type]) {
  77.         case 'PHASE_BEGIN.DISK_CACHE_ENTRY_IMPL':
  78.           var key = update.params.key;
  79.  
  80.           // Merge this source with anything we already know about this key.
  81.           if (cacheEntriesByKey[key]) {
  82.             cacheEntriesByKey[key].merge(cacheEntries[id]);
  83.             cacheEntries[id] = cacheEntriesByKey[key];
  84.           } else {
  85.             cacheEntriesByKey[key] = cacheEntries[id];
  86.           }
  87.           cacheEntriesByKey[key].key = key;
  88.           break;
  89.  
  90.         case 'PHASE_BEGIN.SPARSE_READ':
  91.           cacheEntries[id].readBytes(update.params.offset,
  92.                                       update.params.buff_len);
  93.           cacheEntries[id].sparse = true;
  94.           break;
  95.  
  96.         case 'PHASE_BEGIN.SPARSE_WRITE':
  97.           cacheEntries[id].writeBytes(update.params.offset,
  98.                                        update.params.buff_len);
  99.           cacheEntries[id].sparse = true;
  100.           break;
  101.  
  102.         case 'PHASE_BEGIN.URL_REQUEST_START_JOB':
  103.           requrestURLs[update.source.id] = update.params.url;
  104.           break;
  105.  
  106.         case 'PHASE_NONE.HTTP_TRANSACTION_READ_RESPONSE_HEADERS':
  107.           // Record the total size of the file if this was a range request.
  108.           var range = /content-range:\s*bytes\s*\d+-\d+\/(\d+)/i.exec(
  109.               update.params.headers);
  110.           var key = requrestURLs[update.source.id];
  111.           delete requrestURLs[update.source.id];
  112.           if (range && key) {
  113.             if (!cacheEntriesByKey[key]) {
  114.               cacheEntriesByKey[key] = new media.CacheEntry;
  115.               cacheEntriesByKey[key].key = key;
  116.             }
  117.             cacheEntriesByKey[key].size = range[1];
  118.           }
  119.           break;
  120.       }
  121.     });
  122.   };
  123.  
  124.   media.onRendererTerminated = function(renderId) {
  125.     util.object.forEach(manager.players_, function(playerInfo, id) {
  126.       if (playerInfo.properties['render_id'] == renderId) {
  127.         manager.removePlayer(id);
  128.       }
  129.     });
  130.   };
  131.  
  132.   media.updateAudioComponent = function(component) {
  133.     var uniqueComponentId = component.owner_id + ':' + component.component_id;
  134.     switch (component.status) {
  135.       case 'closed':
  136.         manager.removeAudioComponent(
  137.             component.component_type, uniqueComponentId);
  138.         break;
  139.       default:
  140.         manager.updateAudioComponent(
  141.             component.component_type, uniqueComponentId, component);
  142.         break;
  143.     }
  144.   };
  145.  
  146.   media.onPlayerOpen = function(id, timestamp) {
  147.     manager.addPlayer(id, timestamp);
  148.   };
  149.  
  150.   media.onMediaEvent = function(event) {
  151.     var source = event.renderer + ':' + event.player;
  152.  
  153.     // Although this gets called on every event, there is nothing we can do
  154.     // because there is no onOpen event.
  155.     media.onPlayerOpen(source);
  156.     manager.updatePlayerInfoNoRecord(
  157.         source, event.ticksMillis, 'render_id', event.renderer);
  158.     manager.updatePlayerInfoNoRecord(
  159.         source, event.ticksMillis, 'player_id', event.player);
  160.  
  161.     var propertyCount = 0;
  162.     util.object.forEach(event.params, function(value, key) {
  163.       key = key.trim();
  164.  
  165.       // These keys get spammed *a lot*, so put them on the display
  166.       // but don't log list.
  167.       if (key === 'buffer_start' ||
  168.           key === 'buffer_end' ||
  169.           key === 'buffer_current' ||
  170.           key === 'is_downloading_data') {
  171.         manager.updatePlayerInfoNoRecord(
  172.             source, event.ticksMillis, key, value);
  173.       } else {
  174.         manager.updatePlayerInfo(source, event.ticksMillis, key, value);
  175.       }
  176.       propertyCount += 1;
  177.     });
  178.  
  179.     if (propertyCount === 0) {
  180.       manager.updatePlayerInfo(
  181.           source, event.ticksMillis, 'EVENT', event.type);
  182.     }
  183.   };
  184.  
  185.   // |chrome| is not defined during tests.
  186.   if (window.chrome && window.chrome.send) {
  187.     chrome.send('getEverything');
  188.   }
  189.   return media;
  190. }());
  191.  
  192. // Copyright 2013 The Chromium Authors. All rights reserved.
  193. // Use of this source code is governed by a BSD-style license that can be
  194. // found in the LICENSE file.
  195.  
  196. /**
  197.  * @fileoverview Some utility functions that don't belong anywhere else in the
  198.  * code.
  199.  */
  200.  
  201. var util = (function() {
  202.   var util = {};
  203.   util.object = {};
  204.   /**
  205.    * Calls a function for each element in an object/map/hash.
  206.    *
  207.    * @param obj The object to iterate over.
  208.    * @param f The function to call on every value in the object.  F should have
  209.    * the following arguments: f(value, key, object) where value is the value
  210.    * of the property, key is the corresponding key, and obj is the object that
  211.    * was passed in originally.
  212.    * @param optObj The object use as 'this' within f.
  213.    */
  214.   util.object.forEach = function(obj, f, optObj) {
  215.     'use strict';
  216.     var key;
  217.     for (key in obj) {
  218.       if (obj.hasOwnProperty(key)) {
  219.         f.call(optObj, obj[key], key, obj);
  220.       }
  221.     }
  222.   };
  223.   util.millisecondsToString = function(timeMillis) {
  224.     function pad(num) {
  225.       num = num.toString();
  226.       if (num.length < 2) {
  227.         return '0' + num;
  228.       }
  229.       return num;
  230.     }
  231.  
  232.     var date = new Date(timeMillis);
  233.     return pad(date.getUTCHours()) + ':' + pad(date.getUTCMinutes()) + ':' +
  234.         pad(date.getUTCSeconds()) + ' ' + pad((date.getMilliseconds()) % 1000);
  235.   };
  236.  
  237.   return util;
  238. }());
  239.  
  240. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  241. // Use of this source code is governed by a BSD-style license that can be
  242. // found in the LICENSE file.
  243.  
  244. cr.define('media', function() {
  245.   'use strict';
  246.  
  247.   /**
  248.    * This class represents a file cached by net.
  249.    */
  250.   function CacheEntry() {
  251.     this.read_ = new media.DisjointRangeSet;
  252.     this.written_ = new media.DisjointRangeSet;
  253.     this.available_ = new media.DisjointRangeSet;
  254.  
  255.     // Set to true when we know the entry is sparse.
  256.     this.sparse = false;
  257.     this.key = null;
  258.     this.size = null;
  259.  
  260.     // The <details> element representing this CacheEntry.
  261.     this.details_ = document.createElement('details');
  262.     this.details_.className = 'cache-entry';
  263.     this.details_.open = false;
  264.  
  265.     // The <details> summary line. It contains a chart of requested file ranges
  266.     // and the url if we know it.
  267.     var summary = document.createElement('summary');
  268.  
  269.     this.summaryText_ = document.createTextNode('');
  270.     summary.appendChild(this.summaryText_);
  271.  
  272.     summary.appendChild(document.createTextNode(' '));
  273.  
  274.     // Controls to modify this CacheEntry.
  275.     var controls = document.createElement('span');
  276.     controls.className = 'cache-entry-controls';
  277.     summary.appendChild(controls);
  278.     summary.appendChild(document.createElement('br'));
  279.  
  280.     // A link to clear recorded data from this CacheEntry.
  281.     var clearControl = document.createElement('a');
  282.     clearControl.href = 'javascript:void(0)';
  283.     clearControl.onclick = this.clear.bind(this);
  284.     clearControl.textContent = '(clear entry)';
  285.     controls.appendChild(clearControl);
  286.  
  287.     this.details_.appendChild(summary);
  288.  
  289.     // The canvas for drawing cache writes.
  290.     this.writeCanvas = document.createElement('canvas');
  291.     this.writeCanvas.width = media.BAR_WIDTH;
  292.     this.writeCanvas.height = media.BAR_HEIGHT;
  293.     this.details_.appendChild(this.writeCanvas);
  294.  
  295.     // The canvas for drawing cache reads.
  296.     this.readCanvas = document.createElement('canvas');
  297.     this.readCanvas.width = media.BAR_WIDTH;
  298.     this.readCanvas.height = media.BAR_HEIGHT;
  299.     this.details_.appendChild(this.readCanvas);
  300.  
  301.     // A tabular representation of the data in the above canvas.
  302.     this.detailTable_ = document.createElement('table');
  303.     this.detailTable_.className = 'cache-table';
  304.     this.details_.appendChild(this.detailTable_);
  305.   }
  306.  
  307.   CacheEntry.prototype = {
  308.     /**
  309.      * Mark a range of bytes as read from the cache.
  310.      * @param {int} start The first byte read.
  311.      * @param {int} length The number of bytes read.
  312.      */
  313.     readBytes: function(start, length) {
  314.       start = parseInt(start);
  315.       length = parseInt(length);
  316.       this.read_.add(start, start + length);
  317.       this.available_.add(start, start + length);
  318.       this.sparse = true;
  319.     },
  320.  
  321.     /**
  322.      * Mark a range of bytes as written to the cache.
  323.      * @param {int} start The first byte written.
  324.      * @param {int} length The number of bytes written.
  325.      */
  326.     writeBytes: function(start, length) {
  327.       start = parseInt(start);
  328.       length = parseInt(length);
  329.       this.written_.add(start, start + length);
  330.       this.available_.add(start, start + length);
  331.       this.sparse = true;
  332.     },
  333.  
  334.     /**
  335.      * Merge this CacheEntry with another, merging recorded ranges and flags.
  336.      * @param {CacheEntry} other The CacheEntry to merge into this one.
  337.      */
  338.     merge: function(other) {
  339.       this.read_.merge(other.read_);
  340.       this.written_.merge(other.written_);
  341.       this.available_.merge(other.available_);
  342.       this.sparse = this.sparse || other.sparse;
  343.       this.key = this.key || other.key;
  344.       this.size = this.size || other.size;
  345.     },
  346.  
  347.     /**
  348.      * Clear all recorded ranges from this CacheEntry and redraw this.details_.
  349.      */
  350.     clear: function() {
  351.       this.read_ = new media.DisjointRangeSet;
  352.       this.written_ = new media.DisjointRangeSet;
  353.       this.available_ = new media.DisjointRangeSet;
  354.       this.generateDetails();
  355.     },
  356.  
  357.     /**
  358.      * Helper for drawCacheReadsToCanvas() and drawCacheWritesToCanvas().
  359.      *
  360.      * Accepts the entries to draw, a canvas fill style, and the canvas to
  361.      * draw on.
  362.      */
  363.     drawCacheEntriesToCanvas: function(entries, fillStyle, canvas) {
  364.       // Don't bother drawing anything if we don't know the total size.
  365.       if (!this.size) {
  366.         return;
  367.       }
  368.  
  369.       var width = canvas.width;
  370.       var height = canvas.height;
  371.       var context = canvas.getContext('2d');
  372.       var fileSize = this.size;
  373.  
  374.       context.fillStyle = '#aaa';
  375.       context.fillRect(0, 0, width, height);
  376.  
  377.       function drawRange(start, end) {
  378.         var left = start / fileSize * width;
  379.         var right = end / fileSize * width;
  380.         context.fillRect(left, 0, right - left, height);
  381.       }
  382.  
  383.       context.fillStyle = fillStyle;
  384.       entries.map(function(start, end) {
  385.         drawRange(start, end);
  386.       });
  387.     },
  388.  
  389.     /**
  390.      * Draw cache writes to the given canvas.
  391.      *
  392.      * It should consist of a horizontal bar with highlighted sections to
  393.      * represent which parts of a file have been written to the cache.
  394.      *
  395.      * e.g. |xxxxxx----------x|
  396.      */
  397.     drawCacheWritesToCanvas: function(canvas) {
  398.       this.drawCacheEntriesToCanvas(this.written_, '#00a', canvas);
  399.     },
  400.  
  401.     /**
  402.      * Draw cache reads to the given canvas.
  403.      *
  404.      * It should consist of a horizontal bar with highlighted sections to
  405.      * represent which parts of a file have been read from the cache.
  406.      *
  407.      * e.g. |xxxxxx----------x|
  408.      */
  409.     drawCacheReadsToCanvas: function(canvas) {
  410.       this.drawCacheEntriesToCanvas(this.read_, '#0a0', canvas);
  411.     },
  412.  
  413.     /**
  414.      * Update this.details_ to contain everything we currently know about
  415.      * this file.
  416.      */
  417.     generateDetails: function() {
  418.       function makeElement(tag, content) {
  419.         var toReturn = document.createElement(tag);
  420.         toReturn.textContent = content;
  421.         return toReturn;
  422.       }
  423.  
  424.       this.details_.id = this.key;
  425.       this.summaryText_.textContent = this.key || 'Unknown File';
  426.  
  427.       this.detailTable_.textContent = '';
  428.       var header = document.createElement('thead');
  429.       var footer = document.createElement('tfoot');
  430.       var body = document.createElement('tbody');
  431.       this.detailTable_.appendChild(header);
  432.       this.detailTable_.appendChild(footer);
  433.       this.detailTable_.appendChild(body);
  434.  
  435.       var headerRow = document.createElement('tr');
  436.       headerRow.appendChild(makeElement('th', 'Read From Cache'));
  437.       headerRow.appendChild(makeElement('th', 'Written To Cache'));
  438.       header.appendChild(headerRow);
  439.  
  440.       var footerRow = document.createElement('tr');
  441.       var footerCell = document.createElement('td');
  442.       footerCell.textContent = 'Out of ' + (this.size || 'unkown size');
  443.       footerCell.setAttribute('colspan', 2);
  444.       footerRow.appendChild(footerCell);
  445.       footer.appendChild(footerRow);
  446.  
  447.       var read = this.read_.map(function(start, end) {
  448.         return start + ' - ' + end;
  449.       });
  450.       var written = this.written_.map(function(start, end) {
  451.         return start + ' - ' + end;
  452.       });
  453.  
  454.       var length = Math.max(read.length, written.length);
  455.       for (var i = 0; i < length; i++) {
  456.         var row = document.createElement('tr');
  457.         row.appendChild(makeElement('td', read[i] || ''));
  458.         row.appendChild(makeElement('td', written[i] || ''));
  459.         body.appendChild(row);
  460.       }
  461.  
  462.       this.drawCacheWritesToCanvas(this.writeCanvas);
  463.       this.drawCacheReadsToCanvas(this.readCanvas);
  464.     },
  465.  
  466.     /**
  467.      * Render this CacheEntry as a <li>.
  468.      * @return {HTMLElement} A <li> representing this CacheEntry.
  469.      */
  470.     toListItem: function() {
  471.       this.generateDetails();
  472.  
  473.       var result = document.createElement('li');
  474.       result.appendChild(this.details_);
  475.       return result;
  476.     }
  477.   };
  478.  
  479.   return {
  480.     CacheEntry: CacheEntry
  481.   };
  482. });
  483.  
  484. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  485. // Use of this source code is governed by a BSD-style license that can be
  486. // found in the LICENSE file.
  487.  
  488. cr.define('media', function() {
  489.  
  490.   /**
  491.    * This class represents a collection of non-intersecting ranges. Ranges
  492.    * specified by (start, end) can be added and removed at will. It is used to
  493.    * record which sections of a media file have been cached, e.g. the first and
  494.    * last few kB plus several MB in the middle.
  495.    *
  496.    * Example usage:
  497.    * someRange.add(0, 100);     // Contains 0-100.
  498.    * someRange.add(150, 200);   // Contains 0-100, 150-200.
  499.    * someRange.remove(25, 75);  // Contains 0-24, 76-100, 150-200.
  500.    * someRange.add(25, 149);    // Contains 0-200.
  501.    */
  502.   function DisjointRangeSet() {
  503.     this.ranges_ = {};
  504.   }
  505.  
  506.   DisjointRangeSet.prototype = {
  507.     /**
  508.      * Deletes all ranges intersecting with (start ... end) and returns the
  509.      * extents of the cleared area.
  510.      * @param {int} start The start of the range to remove.
  511.      * @param {int} end The end of the range to remove.
  512.      * @param {int} sloppiness 0 removes only strictly overlapping ranges, and
  513.      *                         1 removes adjacent ones.
  514.      * @return {Object} The start and end of the newly cleared range.
  515.      */
  516.     clearRange: function(start, end, sloppiness) {
  517.       var ranges = this.ranges_;
  518.       var result = {start: start, end: end};
  519.  
  520.       for (var rangeStart in this.ranges_) {
  521.         rangeEnd = this.ranges_[rangeStart];
  522.         // A range intersects another if its start lies within the other range
  523.         // or vice versa.
  524.         if ((rangeStart >= start && rangeStart <= (end + sloppiness)) ||
  525.             (start >= rangeStart && start <= (rangeEnd + sloppiness))) {
  526.           delete ranges[rangeStart];
  527.           result.start = Math.min(result.start, rangeStart);
  528.           result.end = Math.max(result.end, rangeEnd);
  529.         }
  530.       }
  531.  
  532.       return result;
  533.     },
  534.  
  535.     /**
  536.      * Adds a range to this DisjointRangeSet.
  537.      * Joins adjacent and overlapping ranges together.
  538.      * @param {int} start The beginning of the range to add, inclusive.
  539.      * @param {int} end The end of the range to add, inclusive.
  540.      */
  541.     add: function(start, end) {
  542.       if (end < start)
  543.         return;
  544.  
  545.       // Remove all touching ranges.
  546.       result = this.clearRange(start, end, 1);
  547.       // Add back a single contiguous range.
  548.       this.ranges_[Math.min(start, result.start)] = Math.max(end, result.end);
  549.     },
  550.  
  551.     /**
  552.      * Combines a DisjointRangeSet with this one.
  553.      * @param {DisjointRangeSet} ranges A DisjointRangeSet to be squished into
  554.      * this one.
  555.      */
  556.     merge: function(other) {
  557.       var ranges = this;
  558.       other.forEach(function(start, end) { ranges.add(start, end); });
  559.     },
  560.  
  561.     /**
  562.      * Removes a range from this DisjointRangeSet.
  563.      * Will split existing ranges if necessary.
  564.      * @param {int} start The beginning of the range to remove, inclusive.
  565.      * @param {int} end The end of the range to remove, inclusive.
  566.      */
  567.     remove: function(start, end) {
  568.       if (end < start)
  569.         return;
  570.  
  571.       // Remove instersecting ranges.
  572.       result = this.clearRange(start, end, 0);
  573.  
  574.       // Add back non-overlapping ranges.
  575.       if (result.start < start)
  576.         this.ranges_[result.start] = start - 1;
  577.       if (result.end > end)
  578.         this.ranges_[end + 1] = result.end;
  579.     },
  580.  
  581.     /**
  582.      * Iterates over every contiguous range in this DisjointRangeSet, calling a
  583.      * function for each (start, end).
  584.      * @param {function(int, int)} iterator The function to call on each range.
  585.      */
  586.     forEach: function(iterator) {
  587.       for (var start in this.ranges_)
  588.         iterator(start, this.ranges_[start]);
  589.     },
  590.  
  591.     /**
  592.      * Maps this DisjointRangeSet to an array by calling a given function on the
  593.      * start and end of each contiguous range, sorted by start.
  594.      * @param {function(int, int)} mapper Maps a range to an array element.
  595.      * @return {Array} An array of each mapper(range).
  596.      */
  597.     map: function(mapper) {
  598.       var starts = [];
  599.       for (var start in this.ranges_)
  600.         starts.push(parseInt(start));
  601.       starts.sort(function(a, b) {
  602.         return a - b;
  603.       });
  604.  
  605.       var ranges = this.ranges_;
  606.       var results = starts.map(function(s) {
  607.         return mapper(s, ranges[s]);
  608.       });
  609.  
  610.       return results;
  611.     },
  612.  
  613.     /**
  614.      * Finds the maximum value present in any of the contained ranges.
  615.      * @return {int} The maximum value contained by this DisjointRangeSet.
  616.      */
  617.     max: function() {
  618.       var max = -Infinity;
  619.       for (var start in this.ranges_)
  620.         max = Math.max(max, this.ranges_[start]);
  621.       return max;
  622.     },
  623.   };
  624.  
  625.   return {
  626.     DisjointRangeSet: DisjointRangeSet
  627.   };
  628. });
  629.  
  630. // Copyright 2013 The Chromium Authors. All rights reserved.
  631. // Use of this source code is governed by a BSD-style license that can be
  632. // found in the LICENSE file.
  633.  
  634. /**
  635.  * @fileoverview A class for keeping track of the details of a player.
  636.  */
  637.  
  638. var PlayerInfo = (function() {
  639.   'use strict';
  640.  
  641.   /**
  642.    * A class that keeps track of properties on a media player.
  643.    * @param id A unique id that can be used to identify this player.
  644.    */
  645.   function PlayerInfo(id) {
  646.     this.id = id;
  647.     // The current value of the properties for this player.
  648.     this.properties = {};
  649.     // All of the past (and present) values of the properties.
  650.     this.pastValues = {};
  651.  
  652.     // Every single event in the order in which they were received.
  653.     this.allEvents = [];
  654.     this.lastRendered = 0;
  655.  
  656.     this.firstTimestamp_ = -1;
  657.   }
  658.  
  659.   PlayerInfo.prototype = {
  660.     /**
  661.      * Adds or set a property on this player.
  662.      * This is the default logging method as it keeps track of old values.
  663.      * @param timestamp  The time in milliseconds since the Epoch.
  664.      * @param key A String key that describes the property.
  665.      * @param value The value of the property.
  666.      */
  667.     addProperty: function(timestamp, key, value) {
  668.       // The first timestamp that we get will be recorded.
  669.       // Then, all future timestamps are deltas of that.
  670.       if (this.firstTimestamp_ === -1) {
  671.         this.firstTimestamp_ = timestamp;
  672.       }
  673.  
  674.       if (typeof key !== 'string') {
  675.         throw new Error(typeof key + ' is not a valid key type');
  676.       }
  677.  
  678.       this.properties[key] = value;
  679.  
  680.       if (!this.pastValues[key]) {
  681.         this.pastValues[key] = [];
  682.       }
  683.  
  684.       var recordValue = {
  685.         time: timestamp - this.firstTimestamp_,
  686.         key: key,
  687.         value: value
  688.       };
  689.  
  690.       this.pastValues[key].push(recordValue);
  691.       this.allEvents.push(recordValue);
  692.     },
  693.  
  694.     /**
  695.      * Adds or set a property on this player.
  696.      * Does not keep track of old values.  This is better for
  697.      * values that get spammed repeatedly.
  698.      * @param timestamp The time in milliseconds since the Epoch.
  699.      * @param key A String key that describes the property.
  700.      * @param value The value of the property.
  701.      */
  702.     addPropertyNoRecord: function(timestamp, key, value) {
  703.       this.addProperty(timestamp, key, value);
  704.       this.allEvents.pop();
  705.     }
  706.   };
  707.  
  708.   return PlayerInfo;
  709. }());
  710.  
  711. // Copyright 2013 The Chromium Authors. All rights reserved.
  712. // Use of this source code is governed by a BSD-style license that can be
  713. // found in the LICENSE file.
  714.  
  715. /**
  716.  * @fileoverview Keeps track of all the existing PlayerInfo and
  717.  * audio stream objects and is the entry-point for messages from the backend.
  718.  *
  719.  * The events captured by Manager (add, remove, update) are relayed
  720.  * to the clientRenderer which it can choose to use to modify the UI.
  721.  */
  722. var Manager = (function() {
  723.   'use strict';
  724.  
  725.   function Manager(clientRenderer) {
  726.     this.players_ = {};
  727.     this.audioComponents_ = [];
  728.     this.clientRenderer_ = clientRenderer;
  729.   }
  730.  
  731.   Manager.prototype = {
  732.     /**
  733.      * Updates an audio-component.
  734.      * @param componentType Integer AudioComponent enum value; must match values
  735.      * from the AudioLogFactory::AudioComponent enum.
  736.      * @param componentId The unique-id of the audio-component.
  737.      * @param componentData The actual component data dictionary.
  738.      */
  739.     updateAudioComponent: function(componentType, componentId, componentData) {
  740.       if (!(componentType in this.audioComponents_))
  741.         this.audioComponents_[componentType] = {};
  742.       if (!(componentId in this.audioComponents_[componentType])) {
  743.         this.audioComponents_[componentType][componentId] = componentData;
  744.       } else {
  745.         for (var key in componentData) {
  746.           this.audioComponents_[componentType][componentId][key] =
  747.               componentData[key];
  748.         }
  749.       }
  750.       this.clientRenderer_.audioComponentAdded(
  751.           componentType, this.audioComponents_[componentType]);
  752.     },
  753.  
  754.     /**
  755.      * Removes an audio-stream from the manager.
  756.      * @param id The unique-id of the audio-stream.
  757.      */
  758.     removeAudioComponent: function(componentType, componentId) {
  759.       if (!(componentType in this.audioComponents_) ||
  760.           !(componentId in this.audioComponents_[componentType])) {
  761.         return;
  762.       }
  763.  
  764.       delete this.audioComponents_[componentType][componentId];
  765.       this.clientRenderer_.audioComponentRemoved(
  766.           componentType, this.audioComponents_[componentType]);
  767.     },
  768.  
  769.     /**
  770.      * Adds a player to the list of players to manage.
  771.      */
  772.     addPlayer: function(id) {
  773.       if (this.players_[id]) {
  774.         return;
  775.       }
  776.       // Make the PlayerProperty and add it to the mapping
  777.       this.players_[id] = new PlayerInfo(id);
  778.       this.clientRenderer_.playerAdded(this.players_, this.players_[id]);
  779.     },
  780.  
  781.     /**
  782.      * Attempts to remove a player from the UI.
  783.      * @param id The ID of the player to remove.
  784.      */
  785.     removePlayer: function(id) {
  786.       delete this.players_[id];
  787.       this.clientRenderer_.playerRemoved(this.players_, this.players_[id]);
  788.     },
  789.  
  790.     updatePlayerInfoNoRecord: function(id, timestamp, key, value) {
  791.       if (!this.players_[id]) {
  792.         console.error('[updatePlayerInfo] Id ' + id + ' does not exist');
  793.         return;
  794.       }
  795.  
  796.       this.players_[id].addPropertyNoRecord(timestamp, key, value);
  797.       this.clientRenderer_.playerUpdated(this.players_,
  798.                                          this.players_[id],
  799.                                          key,
  800.                                          value);
  801.     },
  802.  
  803.     /**
  804.      *
  805.      * @param id The unique ID that identifies the player to be updated.
  806.      * @param timestamp The timestamp of when the change occured.  This
  807.      * timestamp is *not* normalized.
  808.      * @param key The name of the property to be added/changed.
  809.      * @param value The value of the property.
  810.      */
  811.     updatePlayerInfo: function(id, timestamp, key, value) {
  812.       if (!this.players_[id]) {
  813.         console.error('[updatePlayerInfo] Id ' + id + ' does not exist');
  814.         return;
  815.       }
  816.  
  817.       this.players_[id].addProperty(timestamp, key, value);
  818.       this.clientRenderer_.playerUpdated(this.players_,
  819.                                          this.players_[id],
  820.                                          key,
  821.                                          value);
  822.     }
  823.   };
  824.  
  825.   return Manager;
  826. }());
  827.  
  828. // Copyright 2013 The Chromium Authors. All rights reserved.
  829. // Use of this source code is governed by a BSD-style license that can be
  830. // found in the LICENSE file.
  831.  
  832. var ClientRenderer = (function() {
  833.   var ClientRenderer = function() {
  834.     this.playerListElement = document.getElementById('player-list');
  835.     this.propertiesTable =
  836.         document.getElementById('property-table').querySelector('tbody');
  837.     this.logTable = document.getElementById('log').querySelector('tbody');
  838.     this.graphElement = document.getElementById('graphs');
  839.     this.propertyName = document.getElementById('property-name');
  840.  
  841.     this.selectedPlayer = null;
  842.     this.selectedAudioComponentType = null;
  843.     this.selectedAudioComponentId = null;
  844.     this.selectedAudioCompontentData = null;
  845.  
  846.     this.selectedPlayerLogIndex = 0;
  847.  
  848.     this.filterFunction = function() { return true; };
  849.     this.filterText = document.getElementById('filter-text');
  850.     this.filterText.onkeyup = this.onTextChange_.bind(this);
  851.  
  852.     this.bufferCanvas = document.createElement('canvas');
  853.     this.bufferCanvas.width = media.BAR_WIDTH;
  854.     this.bufferCanvas.height = media.BAR_HEIGHT;
  855.  
  856.     this.clipboardTextarea = document.getElementById('clipboard-textarea');
  857.     this.clipboardButton = document.getElementById('copy-button');
  858.     this.clipboardButton.onclick = this.copyToClipboard_.bind(this);
  859.  
  860.     this.hiddenKeys = ['component_id', 'component_type', 'owner_id'];
  861.   };
  862.  
  863.   function removeChildren(element) {
  864.     while (element.hasChildNodes()) {
  865.       element.removeChild(element.lastChild);
  866.     }
  867.   };
  868.  
  869.   function createButton(text, select_cb) {
  870.     var button = document.createElement('button');
  871.  
  872.     button.appendChild(document.createTextNode(text));
  873.     button.onclick = function() {
  874.       select_cb();
  875.     };
  876.  
  877.     return button;
  878.   };
  879.  
  880.   ClientRenderer.prototype = {
  881.     /**
  882.      * Called when an audio component is added to the collection.
  883.      * @param componentType Integer AudioComponent enum value; must match values
  884.      * from the AudioLogFactory::AudioComponent enum.
  885.      * @param components The entire map of components (name -> dict).
  886.      */
  887.     audioComponentAdded: function(componentType, components) {
  888.       this.redrawAudioComponentList_(componentType, components);
  889.  
  890.       // Redraw the component if it's currently selected.
  891.       if (this.selectedAudioComponentType == componentType &&
  892.           this.selectedAudioComponentId &&
  893.           this.selectedAudioComponentId in components) {
  894.         this.selectAudioComponent_(
  895.             componentType, this.selectedAudioComponentId,
  896.             components[this.selectedAudioComponentId]);
  897.       }
  898.     },
  899.  
  900.     /**
  901.      * Called when an audio component is removed from the collection.
  902.      * @param componentType Integer AudioComponent enum value; must match values
  903.      * from the AudioLogFactory::AudioComponent enum.
  904.      * @param components The entire map of components (name -> dict).
  905.      */
  906.     audioComponentRemoved: function(componentType, components) {
  907.       this.redrawAudioComponentList_(componentType, components);
  908.  
  909.       // Clear the component if it was previously currently selected.
  910.       if (this.selectedAudioComponentType == componentType &&
  911.           !(this.selectedAudioComponentId in components)) {
  912.         this.selectAudioComponent_(null, null, {});
  913.       }
  914.     },
  915.  
  916.     /**
  917.      * Called when a player is added to the collection.
  918.      * @param players The entire map of id -> player.
  919.      * @param player_added The player that is added.
  920.      */
  921.     playerAdded: function(players, playerAdded) {
  922.       this.redrawPlayerList_(players);
  923.     },
  924.  
  925.     /**
  926.      * Called when a playre is removed from the collection.
  927.      * @param players The entire map of id -> player.
  928.      * @param player_added The player that was removed.
  929.      */
  930.     playerRemoved: function(players, playerRemoved) {
  931.       this.redrawPlayerList_(players);
  932.     },
  933.  
  934.     /**
  935.      * Called when a property on a player is changed.
  936.      * @param players The entire map of id -> player.
  937.      * @param player The player that had its property changed.
  938.      * @param key The name of the property that was changed.
  939.      * @param value The new value of the property.
  940.      */
  941.     playerUpdated: function(players, player, key, value) {
  942.       if (player === this.selectedPlayer) {
  943.         this.drawProperties_(player.properties);
  944.         this.drawLog_();
  945.         this.drawGraphs_();
  946.       }
  947.       if (key === 'name' || key === 'url') {
  948.         this.redrawPlayerList_(players);
  949.       }
  950.     },
  951.  
  952.     redrawAudioComponentList_: function(componentType, components) {
  953.       function redrawList(renderer, baseName, element) {
  954.         var fragment = document.createDocumentFragment();
  955.         for (id in components) {
  956.           var li = document.createElement('li');
  957.           var friendlyName = baseName + ' ' + id;
  958.           li.appendChild(createButton(
  959.               friendlyName, renderer.selectAudioComponent_.bind(
  960.                   renderer, componentType, id, components[id], friendlyName)));
  961.           fragment.appendChild(li);
  962.         }
  963.         removeChildren(element);
  964.         element.appendChild(fragment);
  965.       }
  966.  
  967.       switch (componentType) {
  968.         case 0:
  969.           redrawList(this, 'Controller', document.getElementById(
  970.               'audio-input-controller-list'));
  971.           break;
  972.         case 1:
  973.           redrawList(this, 'Controller', document.getElementById(
  974.               'audio-output-controller-list'));
  975.           break;
  976.         case 2:
  977.           redrawList(this, 'Stream', document.getElementById(
  978.               'audio-output-stream-list'));
  979.           break;
  980.         default:
  981.           break;
  982.       }
  983.     },
  984.  
  985.     selectAudioComponent_: function(
  986.           componentType, componentId, componentData, friendlyName) {
  987.       this.selectedPlayer = null;
  988.       this.selectedAudioComponentType = componentType;
  989.       this.selectedAudioComponentId = componentId;
  990.       this.selectedAudioCompontentData = componentData;
  991.       this.drawProperties_(componentData);
  992.       removeChildren(this.logTable);
  993.       removeChildren(this.graphElement);
  994.  
  995.       removeChildren(this.propertyName);
  996.       this.propertyName.appendChild(document.createTextNode(friendlyName));
  997.     },
  998.  
  999.     redrawPlayerList_: function(players) {
  1000.       var fragment = document.createDocumentFragment();
  1001.       for (id in players) {
  1002.         var player = players[id];
  1003.         var usableName = player.properties.name ||
  1004.             player.properties.url ||
  1005.             'Player ' + player.id;
  1006.  
  1007.         var li = document.createElement('li');
  1008.         li.appendChild(createButton(
  1009.             usableName, this.selectPlayer_.bind(this, player)));
  1010.         fragment.appendChild(li);
  1011.       }
  1012.       removeChildren(this.playerListElement);
  1013.       this.playerListElement.appendChild(fragment);
  1014.     },
  1015.  
  1016.     selectPlayer_: function(player) {
  1017.       this.selectedPlayer = player;
  1018.       this.selectedPlayerLogIndex = 0;
  1019.       this.selectedAudioComponentType = null;
  1020.       this.selectedAudioComponentId = null;
  1021.       this.selectedAudioCompontentData = null;
  1022.       this.drawProperties_(player.properties);
  1023.  
  1024.       removeChildren(this.logTable);
  1025.       removeChildren(this.graphElement);
  1026.       this.drawLog_();
  1027.       this.drawGraphs_();
  1028.       removeChildren(this.propertyName);
  1029.       this.propertyName.appendChild(document.createTextNode('Player'));
  1030.     },
  1031.  
  1032.     drawProperties_: function(propertyMap) {
  1033.       removeChildren(this.propertiesTable);
  1034.       var sortedKeys = Object.keys(propertyMap).sort();
  1035.       for (var i = 0; i < sortedKeys.length; ++i) {
  1036.         var key = sortedKeys[i];
  1037.         if (this.hiddenKeys.indexOf(key) >= 0)
  1038.           continue;
  1039.  
  1040.         var value = propertyMap[key];
  1041.         var row = this.propertiesTable.insertRow(-1);
  1042.         var keyCell = row.insertCell(-1);
  1043.         var valueCell = row.insertCell(-1);
  1044.  
  1045.         keyCell.appendChild(document.createTextNode(key));
  1046.         valueCell.appendChild(document.createTextNode(value));
  1047.       }
  1048.     },
  1049.  
  1050.     appendEventToLog_: function(event) {
  1051.       if (this.filterFunction(event.key)) {
  1052.         var row = this.logTable.insertRow(-1);
  1053.  
  1054.         var timestampCell = row.insertCell(-1);
  1055.         timestampCell.classList.add('timestamp');
  1056.         timestampCell.appendChild(document.createTextNode(
  1057.             util.millisecondsToString(event.time)));
  1058.         row.insertCell(-1).appendChild(document.createTextNode(event.key));
  1059.         row.insertCell(-1).appendChild(document.createTextNode(event.value));
  1060.       }
  1061.     },
  1062.  
  1063.     drawLog_: function() {
  1064.       var toDraw = this.selectedPlayer.allEvents.slice(
  1065.           this.selectedPlayerLogIndex);
  1066.       toDraw.forEach(this.appendEventToLog_.bind(this));
  1067.       this.selectedPlayerLogIndex = this.selectedPlayer.allEvents.length;
  1068.     },
  1069.  
  1070.     drawGraphs_: function() {
  1071.       function addToGraphs(name, graph, graphElement) {
  1072.         var li = document.createElement('li');
  1073.         li.appendChild(graph);
  1074.         li.appendChild(document.createTextNode(name));
  1075.         graphElement.appendChild(li);
  1076.       }
  1077.  
  1078.       var url = this.selectedPlayer.properties.url;
  1079.       if (!url) {
  1080.         return;
  1081.       }
  1082.  
  1083.       var cache = media.cacheForUrl(url);
  1084.  
  1085.       var player = this.selectedPlayer;
  1086.       var props = player.properties;
  1087.  
  1088.       var cacheExists = false;
  1089.       var bufferExists = false;
  1090.  
  1091.       if (props['buffer_start'] !== undefined &&
  1092.           props['buffer_current'] !== undefined &&
  1093.           props['buffer_end'] !== undefined &&
  1094.           props['total_bytes'] !== undefined) {
  1095.         this.drawBufferGraph_(props['buffer_start'],
  1096.                               props['buffer_current'],
  1097.                               props['buffer_end'],
  1098.                               props['total_bytes']);
  1099.         bufferExists = true;
  1100.       }
  1101.  
  1102.       if (cache) {
  1103.         if (player.properties['total_bytes']) {
  1104.           cache.size = Number(player.properties['total_bytes']);
  1105.         }
  1106.         cache.generateDetails();
  1107.         cacheExists = true;
  1108.  
  1109.       }
  1110.  
  1111.       if (!this.graphElement.hasChildNodes()) {
  1112.         if (bufferExists) {
  1113.           addToGraphs('buffer', this.bufferCanvas, this.graphElement);
  1114.         }
  1115.         if (cacheExists) {
  1116.           addToGraphs('cache read', cache.readCanvas, this.graphElement);
  1117.           addToGraphs('cache write', cache.writeCanvas, this.graphElement);
  1118.         }
  1119.       }
  1120.     },
  1121.  
  1122.     drawBufferGraph_: function(start, current, end, size) {
  1123.       var ctx = this.bufferCanvas.getContext('2d');
  1124.       var width = this.bufferCanvas.width;
  1125.       var height = this.bufferCanvas.height;
  1126.       ctx.fillStyle = '#aaa';
  1127.       ctx.fillRect(0, 0, width, height);
  1128.  
  1129.       var scale_factor = width / size;
  1130.       var left = start * scale_factor;
  1131.       var middle = current * scale_factor;
  1132.       var right = end * scale_factor;
  1133.  
  1134.       ctx.fillStyle = '#a0a';
  1135.       ctx.fillRect(left, 0, middle - left, height);
  1136.       ctx.fillStyle = '#aa0';
  1137.       ctx.fillRect(middle, 0, right - middle, height);
  1138.     },
  1139.  
  1140.     copyToClipboard_: function() {
  1141.       var properties = this.selectedAudioCompontentData ||
  1142.           this.selectedPlayer.properties || false;
  1143.       if (!properties) {
  1144.         return;
  1145.       }
  1146.       var stringBuffer = [];
  1147.  
  1148.       for (var key in properties) {
  1149.         var value = properties[key];
  1150.         stringBuffer.push(key.toString());
  1151.         stringBuffer.push(': ');
  1152.         stringBuffer.push(value.toString());
  1153.         stringBuffer.push('\n');
  1154.       }
  1155.  
  1156.       this.clipboardTextarea.value = stringBuffer.join('');
  1157.       this.clipboardTextarea.classList.remove('hiddenClipboard');
  1158.       this.clipboardTextarea.focus();
  1159.       this.clipboardTextarea.select();
  1160.  
  1161.       // Hide the clipboard element when it loses focus.
  1162.       this.clipboardTextarea.onblur = function(event) {
  1163.         setTimeout(function(element) {
  1164.           event.target.classList.add('hiddenClipboard');
  1165.         }, 0);
  1166.       };
  1167.     },
  1168.  
  1169.     onTextChange_: function(event) {
  1170.       var text = this.filterText.value.toLowerCase();
  1171.       var parts = text.split(',').map(function(part) {
  1172.         return part.trim();
  1173.       }).filter(function(part) {
  1174.         return part.trim().length > 0;
  1175.       });
  1176.  
  1177.       this.filterFunction = function(text) {
  1178.         text = text.toLowerCase();
  1179.         return parts.length === 0 || parts.some(function(part) {
  1180.           return text.indexOf(part) != -1;
  1181.         });
  1182.       };
  1183.  
  1184.       if (this.selectedPlayer) {
  1185.         removeChildren(this.logTable);
  1186.         this.selectedPlayerLogIndex = 0;
  1187.         this.drawLog_();
  1188.       }
  1189.     },
  1190.   };
  1191.  
  1192.   return ClientRenderer;
  1193. })();
  1194.  
  1195.  
  1196. media.initialize(new Manager(new ClientRenderer()));
  1197.